// CoreCalc, a spreadsheet core implementation 

// ----------------------------------------------------------------------
// Copyright (c) 2006 Peter Sestoft and Thomas S. Iversen

// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use, copy,
// modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

//  * The above copyright notice and this permission notice shall be
//    included in all copies or substantial portions of the Software.

//  * The software is provided "as is", without warranty of any kind,
//    express or implied, including but not limited to the warranties of
//    merchantability, fitness for a particular purpose and
//    noninfringement.  In no event shall the authors or copyright
//    holders be liable for any claim, damages or other liability,
//    whether in an action of contract, tort or otherwise, arising from,
//    out of or in connection with the software or the use or other
//    dealings in the software.
// ----------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Text;

// A Sheet contains Cells and belongs to a Workbook

namespace CoreCalc {
    public delegate void CellChanged(int col, int row, Sheet sheet);

  public sealed class Sheet {
    public event CellChanged OnCellChanged;
    public const int cols = 10, rows = 8001;   // Default sheet size
    private String name;
    public readonly Workbook workbook;       // Non-null
    private Cell[,] cells;   // Indexed by (col, row); unused cells are null 

    private bool functionSheet;

    public Sheet(Workbook workbook, String name, bool functionSheet)
      : this(workbook, name, cols, rows, functionSheet) {
    }

    public Sheet(Workbook workbook, String name, int cols, int rows, bool functionSheet) {
      this.workbook = workbook;
      this.name = name;
      this.cells = new Cell[cols, rows];
      this.functionSheet = functionSheet;
      workbook.AddSheet(this);
    }

    // Recalculate all cells by evaluating their contents
    public void Recalculate() {
      for (int col = 0; col < Cols; col++)
        for (int row = 0; row < Rows; row++) {
          Cell cell = cells[col, row];
          if (cell != null)
            cell.Eval(this, col, row);
        }
    }

    // Show the contents of all non-null cells
    public void ShowAll(Act<int,int,String> show) {
      for (int col = 0; col < Cols; col++)
        for (int row = 0; row < Rows; row++) {
          Cell cell = cells[col, row];
          show(col, row, cell != null ? ShowValue(col, row) : null);
        }
    }

    // Reset recomputation flags after a circularity has been found
    public void Reset() {
      foreach (Cell cell in cells)
        if (cell != null)
          cell.Reset();
    }

    // Insert cell from parsed constant or formula in sheet[col, row]
    public Cell InsertCell(String text, CellAddr ca) {
      return this[ca] = Cell.Parse(text, workbook, ca.col, ca.row); 
    }

    // Insert cell, which must be Formula, as array formula 
    // in area ((ulCol,ulRow), (lrCol, lrRow))
    public void InsertArrayFormula(Cell cell, int col, int row,
                                   CellAddr ulCa, CellAddr lrCa) {
      Formula formula = cell as Formula;
      if (cell == null)
        throw new Exception("Invalid array formula");
      else {
        CachedArrayFormula cmf
          = new CachedArrayFormula(formula, col, row, ulCa, lrCa);
        int cols = lrCa.col - ulCa.col + 1, rows = lrCa.row - ulCa.row + 1;
        for (int c = 0; c < cols; c++)
          for (int r = 0; r < rows; r++)
            cells[ulCa.col + c, ulCa.row + r] = new ArrayFormula(cmf, c, r);
      }
    }

    // Copy cell to cell address ca
    public void PasteCell(Cell cell, CellAddr ca) {
      PasteCell(cell, ca, 1, 1);
    }

    // Copy cell to area ((col,row), (col+cols-1,row+rows-1))
    public void PasteCell(Cell cell, CellAddr ca, int cols, int rows) {
      if (cell is Formula) {
        // Clone cache but share expression f.e between all target cells
        Formula f = (Formula)cell;
        for (int c = 0; c < cols; c++)
          for (int r = 0; r < rows; r++)
            cells[ca.col + c, ca.row + r] = new Formula(f);
      }
      else if (cell is ConstCell) {
        // Share constant cell between all target cells
        for (int c = 0; c < cols; c++)
          for (int r = 0; r < rows; r++)
            cells[ca.col + c, ca.row + r] = cell;
      }
      else
        throw new Exception("Cannot copy cell: " + cell);
    }

    // Move cell (fromCol, fromRow) to cell (col,row)
    public void MoveCell(int fromCol, int fromRow, int col, int row) {
      Cell cell = cells[fromCol, fromRow];
      cells[col, row] = cell.MoveContents(col - fromCol, row - fromRow);
    }

    // Insert N new rows just before row R >= 0
    public void InsertRowCols(int R, int N, bool doRows) {
      // Check that this will not split a array formula
      if (R >= 1)
        if (doRows) {
          for (int col = 0; col < Cols; col++) {
            Cell cell = cells[col, R - 1];
            ArrayFormula mf = cell as ArrayFormula;
            if (mf != null && mf.Contains(col, R))
              throw new Exception("Row insert would split array formula");
          }
        }
        else {
          for (int row = 0; row < Rows; row++) {
            Cell cell = cells[R - 1, row];
            ArrayFormula mf = cell as ArrayFormula;
            if (mf != null && mf.Contains(R, row))
              throw new Exception("Column insert would split array formula");
          }
        }
      // Adjust formulas in all sheets.  The dictionary records adjusted
      // expressions to preserve sharing of expressions where possible.
      Dictionary<Expr, Adjusted<Expr>> adjusted
        = new Dictionary<Expr, Adjusted<Expr>>();
      foreach (Sheet sheet in workbook) {
        Cell[,] cs = sheet.cells;
        for (int r = 0; r < sheet.Rows; r++)
          for (int c = 0; c < sheet.Cols; c++) {
            Cell cell = cs[c, r];
            if (cell != null)
              cell.InsertRowCols(adjusted, this, sheet == this, R, N,
               doRows ? r : c, doRows);
          }
      }
      if (doRows) {
        // Move the rows R, R+1, ... later by N rows in current sheet
        for (int r = Rows - 1; r >= R + N; r--)
          for (int c = 0; c < Cols; c++)
            cells[c, r] = cells[c, r - N];
        // Finally, null out the fresh rows
        for (int r = 0; r < N; r++)
          for (int c = 0; c < Cols; c++)
            cells[c, r + R] = null;
      }
      else {
        // Move the columns R, R+1, ... later by N columns in current sheet
        for (int c = Cols - 1; c >= R + N; c--)
          for (int r = 0; r < Rows; r++)
            cells[c, r] = cells[c - N, r];
        // Finally, null out the fresh columns
        for (int c = 0; c < N; c++)
          for (int r = 0; r < Rows; r++)
            cells[c + R, r] = null;
      }
    }

    // Show contents, if any, of cell at (col, row)
    public String Show(int col, int row) {
      if (0 <= col && col < Cols && 0 <= row && row < Rows) {
        Cell cell = cells[col, row];
        if (cell != null)
          return cell.Show(col, row, workbook.Format);
      }
      return null;
    }

    // Show value (or contents), if any, of cell (col, row)
    public String ShowValue(int col, int row) {
      if (0 <= col && col < Cols && 0 <= row && row < Rows) {
        Cell cell = cells[col, row];
        if (cell != null)
          if (workbook.Format.ShowFormulas)
            return cell.Show(col, row, workbook.Format);
          else
            return cell.ShowValue(this, col, row);
      }
      return null;
    }

    // Get and set cell contents
    public Cell this[int col, int row] {
      get {
        return cells[col, row];
      }
      set {
          if (cells[col, row] != value)
          {
              CellChanged(col, row);
              cells[col, row] = value;
          }
      }
    }

      private void CellChanged(int col, int row)
      {
          if (OnCellChanged != null)
              OnCellChanged(col, row, this);
      }

    public Cell this[CellAddr ca] {
      get {
        return cells[ca.col, ca.row];
      }
      set {
        cells[ca.col, ca.row] = value;
      }
    }

    public int Cols {
      get { return cells.GetLength(0); }
    }

    public int Rows {
      get { return cells.GetLength(1); }
    }

    public String Name {
      get { return name; }
      set { name = value; }
    }

    public bool IsFunctionSheet {
      get { return functionSheet; }
      set { functionSheet = value; }
    }

    public override string ToString() {
      return name;
    }
  }
}
